#include "cheetah/graphics/model_gl.h"
#include <glm/gtc/matrix_transform.hpp>
#include <GL/glew.h>
#include "cheetah/graphics/utils.h"

namespace cheetah {
namespace graphics {
using namespace cheetah::core;
using namespace cheetah::graphics;

ModelGL::ModelGL(const Hint hint) : hint_(hint) {    
  this->Initialize();
}

ModelGL::ModelGL() : hint_(Hint()) {
  this->Initialize();
}

void ModelGL::Initialize() {
  vert_vbo_ = 0;
  idx_vbo_ = 0;
  vertex_slot_ = 0;
  normal_slot_ = 2;
  vert_vbo_size_ = 0;
  idx_vbo_size_ = 0;  
}

ModelGL::~ModelGL() {  
  this->ReleaseVBOs();
}

inline void ModelGL::ReleaseVBOs() {
  glDeleteBuffers(1, &vert_vbo_);
  glDeleteBuffers(1, &idx_vbo_);
  vert_vbo_size_ = 0;
  idx_vbo_size_ = 0;
  // will glDeleteBuffers set vert_vbo_ to 0 ?
  vert_vbo_ = 0;
  idx_vbo_ = 0;
}

inline int ModelGL::AllocateVertVBO(const int size) {
  int buff_size = hint_.vert_buff_size;
  if (size > buff_size) buff_size = size;

  if (buff_size > this->vert_vbo_size_) {
    glDeleteBuffers(1, &vert_vbo_);
    glGenBuffers(1, &vert_vbo_);

    glBindBuffer(GL_ARRAY_BUFFER, vert_vbo_);
    // the buffer stores vertices and normals
    glBufferData(GL_ARRAY_BUFFER, buff_size * 2, NULL, GL_STATIC_DRAW);

    if (CheckErrorsGL("AllocateVertVBO")) return kFailed;
    this->vert_vbo_size_ = buff_size;
  } else {
    glBindBuffer(GL_ARRAY_BUFFER, vert_vbo_);
  }

  return kOK;
}

inline int ModelGL::AllocateIdxVBO(const int size) {
  int buff_size = hint_.idx_buff_size;
  if (size > buff_size) buff_size = size;

  if (buff_size > this->idx_vbo_size_) {
    glDeleteBuffers(1, &idx_vbo_);
    glGenBuffers(1, &idx_vbo_);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_vbo_);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, buff_size, NULL, GL_STATIC_DRAW);

    if (CheckErrorsGL("AllocateVertVBO")) return kFailed;
    this->idx_vbo_size_ = buff_size;
  } else {
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_vbo_);
  }
  return kOK;
}

int ModelGL::LoadModel(const TriangleMesh *mesh) {
  assert(NULL != mesh);
  if (NULL == mesh) return kInvalidArg;

  const int vert_size = mesh->vertices_size();
  const int idx_size = mesh->indices_size();

  int ret = this->AllocateIdxVBO(idx_size);
  if (kOK != ret) return ret;  
  ret = this->AllocateVertVBO(vert_size);
  if (kOK != ret) return ret;

  mesh_ = mesh;
  const int normal_offset = vert_size;  
  glBufferSubData(GL_ARRAY_BUFFER, 0, vert_size, mesh->vertices());
  glBufferSubData(GL_ARRAY_BUFFER, vert_size, vert_size, mesh->normals());
  glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_size, mesh->indices());
  
 /* glVertexAttribPointer(vertex_slot_, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glVertexAttribPointer(normal_slot_, 3, GL_FLOAT, GL_FALSE, 0, 
                        (GLvoid*)vert_size);*/
  
  if (CheckErrorsGL("LoadModel")) return kFailed;  

  return kOK;
}

void ModelGL::BeginDraw() const {
  glEnableVertexAttribArray(vertex_slot_);
  glEnableVertexAttribArray(normal_slot_);
  glBindBuffer(GL_ARRAY_BUFFER, vert_vbo_);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_vbo_);
  
  glVertexAttribPointer(vertex_slot_, 3, GL_FLOAT, GL_FALSE, 0, NULL);
  glVertexAttribPointer(normal_slot_, 3, GL_FLOAT, GL_FALSE, 0, 
                        (GLvoid*)mesh_->vertices_size());
}

void ModelGL::EndDraw() const {
  glDisableVertexAttribArray(vertex_slot_);
  glDisableVertexAttribArray(normal_slot_);
}

void ModelGL::DrawInstanced(const int n_instances, const int opts) const {
  this->BeginDraw();
  glDrawElementsInstanced(GL_TRIANGLES, mesh_->n_triangles() * 3,
                          GL_UNSIGNED_INT, NULL, n_instances);
  this->EndDraw();
}

void ModelGL::Draw(const int opts) const {
  this->BeginDraw();  
  glDrawElements(GL_TRIANGLES, mesh_->n_triangles() * 3, GL_UNSIGNED_INT,
                 NULL);
  this->EndDraw();
}

void ModelGL::DrawBound() const {
  const AABB &bound = mesh_->Bound();
  DrawCube(bound.minP, bound.maxP);
}

const uint32 Quad2DGL::indices_[6] = { 0, 1, 2, 0, 2, 3 };

void Quad2DGL::Draw() {
  glEnableVertexAttribArray(vertex_slot_);
  glEnableVertexAttribArray(tex_coord_slot_);

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_vbo_);
  glBindBuffer(GL_ARRAY_BUFFER, vert_vbo_);

  glVertexAttribPointer(vertex_slot_, 2, GL_FLOAT, GL_FALSE, 0, 0);
  glVertexAttribPointer(tex_coord_slot_, 2, GL_FLOAT, GL_FALSE, 0, 
                        (GLvoid*)sizeof(vertices_));

  // the data type must be right
  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

  glDisableVertexAttribArray(vertex_slot_);
  glDisableVertexAttribArray(tex_coord_slot_);
  CheckErrorsGL("Quad2DGL::Draw");
}

void Quad2DGL::CreateVBO() {
  glGenBuffers(1, &vert_vbo_);
  glGenBuffers(1, &idx_vbo_);
  
  const int vert_size = sizeof(vertices_);
  const int tex_coord_size = sizeof(tex_coords_);
  glBindBuffer(GL_ARRAY_BUFFER, vert_vbo_);
  glBufferData(GL_ARRAY_BUFFER, vert_size + tex_coord_size, NULL,
               GL_STATIC_DRAW);
  glBufferSubData(GL_ARRAY_BUFFER, 0, vert_size, vertices_);
  glBufferSubData(GL_ARRAY_BUFFER, vert_size, tex_coord_size, tex_coords_); 

  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idx_vbo_);  
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices_), indices_, 
               GL_STATIC_DRAW); 
  CheckErrorsGL("Quad2DGL::CreateVBO");
}

ScreenQuad::ScreenQuad() : Quad2DGL(0, 0, 1, 1) {
  mvp_ = glm::ortho(0, 1, 0, 1);
}

} // graphics
} // cheetah
